7.1
More...
To
keep these pages manageable, I decided to split my old 2D
Graphics page into two parts. You're now entering the second stage.
As Ben Liebrand said: 'Escape while you can... Run run! Bwoaahahahaha!"
(The
quote above may only mean something those those growing up in the Netherlands,
listening on their transistor radios to the legendary Curry en Van Inkel...)
7.2
Moving sprites
Framerate
Your
graphics card is sending an image to the monitor, a certain number of times
per second. Unfortunately not all hardware configurations do so with the
same frequency. The sample code of RotateSprite3D
a little back can be used to show the problem. Typically LCD's will run
at 60 frames per second, called the 'refresh rate', but you cannot count
on that! Good ol' CRT's run at all kinds of frequencies, randing from 50
to 100 hz...
Using
OpenScreen() with the #PB_Screen_WaitSynchronization or #PB_Screen_SmartSynchronization
flag makes FlipBuffers() wait
for the vertical blank, before it 'flips the buffer'. Duh. Vertical blank?
The vertical blank is a brief 'pause' between two screen refreshes. If
we change the screen in that period, the user will see a completely new
frame, without any 'jerking' or 'tearing'. So, do we want it? Yes, we do
want it.
When
our program hits the Flipbuffers() statement it will wait for the next
screen to be drawn (if the right option was used with OpenScreen() of course...
So, if computer A has a refresh of 100 frames per second, and computer
B has a refresh of 60 frames per second, then on computer A the program
will start / stop a 100 times per second, and on computer B that will only
happen 60 times. If our code is tied into the same loop as FlipBuffers(),
the speed of our code would thus vary... In other words, on computer A
the program will do it's calculations 100 times per second, whilst on the
other machine it will only do 60 calculations per second.
So?
Imagine you move a sprite 1 pixel per calculation, that means on computer
A the sprite will move 100 pixels per second whilst on computer B it will
only move 60 pixels per second. That may be fine for certain applications,
but for games that's somewhat... euh... undesirable :-)
The
example
with RotateSprite3D() can be tweaked a little to show this. Uncomment these
two lines:
;
framerate = 120
;
SetFrameRate(framerate)
We are
now setting our own refresh speed at 120 frames per second. If we run the
code again... nothing changed! That's because FlipBuffers() is still waiting
for the vertical blank, check out the OpenScreen() command, and replace
#PB_Screen_WaitSynchronization with #PB_Screen_NoSynchronization and run...
Now we will get our chosen speed, ignoring the actual refresh rate of the
screen.
Refreshrate
vs. framerate
Once
more... An (old CRT) monitor displays it's image 'n' times per second.
It draws horizontal line after horizontal line, then at the end jumps back
to the start to do it all over again. That jumping back to the start is
called the vertical blank. The number of times it updates the screen is
called the refreshrate.
You
can set the refresh rate for a full screen using:
SetRefreshRate()
...but
if that actually does anything depends on your videocard and settings (and
incorrect settings could eventually damage your (older) monitor). Thus
I would suggest to only do these kind of things after asking the user's
confirmation / permission.
You
can detect the refreshrate (of monitor 0 ie. desktop 0) using:
ExamineDesktops()
refreshrate
= DesktopFrequency(0)
The data
on your screen is also updated a number of times per second, called the
framerate. The framerate and the refreshrate don't have to be the same
frequency. In the paragraph above we modified the code to run at a framerate
unrelated to the refresh rate, by telling FlipBuffers() NOT to wait for
the screen refreshes, but to wait for our own set framerate using SetFrameRate()
and FlipBuffers(#PB_Screen_NoSynchronization).
Obviously,
when our drawing is updated at a fixed rate, and we don't care about the
actual screen refresh rate, we may run into an effect called 'tearing'.
This happens becuase we change the image whilst it is being shown on the
screen, so it's better to update the image during the vertical blank.
Many
games show very high framerates, but no tearing. They draw their images
at a higher frequency, but the image shown to the user is still matched
to the vertical blank. In other words, the game's engine may draw 200 frames
per second, but you are only seeing 60 of them, the rest is thrown away.
For
now I'll keep the framerate tied to the refreshrate, ie.
ExamineDesktops()
refreshrate
= DesktopFrequency(0)
framerate
= refreshrate
Refreshrate
independent sprite movement, framerate independent sprite speeds
Ah,
what a lovely sentence :-)
There
are many ways to create framerate ie. refreshrate independent sprite movement.
You could totally disconnect things-on-screen from things-behind-the-scenes,
or you could calculate the movement per frame so the movement per second
is the same on every system regardless of framerate. Let's try that approach
first... (And I'm not so sure I can handle the latter, but perhaps I'll
try it lateron...)
The
trick is to express movement in distance per second and then figure out
how much that is per frame. Let's say we move a sprite by 30 pixels each
second, and our framerate is 60 frames per second:
framerate
= 60
speedpersecond.f
= 30
speedperframe.f
= speedpersecond / framerate
So, if
we would move our sprite with 30 pixels per second, and we have 60 frames
per second, then we would have to move it 30 / 60 = 0.5 pixels per frame.
(60 frames = 1 second = 60 x 0.5 = 30 pixels per second)
Let's
start with the framework. We'll create a window,
put a screen on it,
and handle all events properly
in an endles Repeat / Until
loop. We'll set action.i to #f_exit to tell the program we want to quit,
and we're checking the [Esc] key as well as [Alt]+[F4] (which causes a
#PB_Event_CloseWindow). I like to use Enumeration
to make sure that each window, gadget,
image
and action has its own unique number. It makes debugging
(and thus life :-)) easier. (Yes, if you paid attention all those topics
passed in the previous pages :-))
;
survival guide 7_2_100 sprite engine start
;
pb 4.51 directx9
;
EnableExplicit
;
InitSprite()
InitKeyboard()
;
Enumeration
;
; windows
;
#w_main_nr
;
; actions
;
#f_exit
#f_none
;
; images
;
#i_rocket
;
EndEnumeration
;
ExamineDesktops()
Global
desktop_width.i = DesktopWidth(0)
Global
desktop_height.i = DesktopHeight(0)
Global
desktop_depth.i = DesktopDepth(0)
;
;
open a maximized window the same size as the desktop and without a border
;
Global
w_main_h.i = OpenWindow(#w_main_nr,0,0,desktop_width,desktop_height,"Sprite
Engine",#PB_Window_BorderLess|#PB_Window_ScreenCentered|#PB_Window_Maximize)
;
;
open a screen the same size as the window (ie. covers the whole desktop)
;
OpenWindowedScreen(w_main_h.i,0,0,desktop_width,desktop_height,0,0,0,#PB_Screen_SmartSynchronization)
;
;
endless loop handling events
;
Global
action.i = #f_none
Repeat
Global event = WindowEvent()
Select event
Case #PB_Event_CloseWindow
action = #f_exit
Case 0
;
; there were no events, so let's do our graphical stuff
;
ExamineKeyboard()
If KeyboardPushed(#PB_Key_Escape)
action = #f_exit
EndIf
;
; and show things on the screen
;
FlipBuffers()
;
EndSelect
Until
action = #f_exit
;
;
smoothly close screen and window
;
CloseScreen()
CloseWindow(#w_main_nr)
If you
run the above, the screen turns black, and nothing happens. Well, that's
the idea, as we're not drawing anything on it yet!
We
want to display multiple sprites, all floating around on the screen, all
with different directions and speeds. To store information about each sprite,
we'll create a structure
that contains all information we need: its image, its speed, its direction,
its position. As we want to deal with multiple sprites at the same time,
we'll create an array to keep
track of all of them and we'll check all elements using a For
/ Next loop. (Ah, more references to previous topics :-))
Let's
expand the framework above, by creating an two images (one for a sprite,
one for a rock). We'll then create four sprites, one of them our 'rocket',
three of them our 'rocks'. For now, we'll just put them on a static spot
on the screen. We'll dimension the array to contain a maximum of 100 objects,
and we'll use a flag in the structure to tell our program which objects
(sprites) are alive and which ones are not. Each object gets its own 3D
sprite assigned so we can rotate them independently.
So...
;
survival guide 7_2_200 sprite engine 4 static sprites
;
pb 4.51 directx9
;
EnableExplicit
;
;
*** initialize
;
InitSprite()
InitSprite3D()
InitKeyboard()
;
;
make sure our gui elements, images etc. have unique numbers
;
Enumeration
#w_main_nr
; main window
;
#f_exit
; quit
#f_none
; no action (keep looping)
;
#i_vectoid
; rocket 'v' shaped image
#i_rock
; rock image
;
EndEnumeration
;
;
create a structure that will contain all information on each object (sprite)
;
Structure
object
alive.i
; set to #true to enable this object
sprite3d_nr.i
; 3d sprite used
a.f
; angle ie. object orientation
z.f
; object size factor (1 = original size)
x.f
; x-coordinate
y.f
; y-coordinate
EndStructure
;
;
create an array that can contain 100 objects
;
#objects_n
= 100
Dim
object.object(#objects_n) ; so
the array() is called 'object' and is of type 'object'
;
;
*** get information on the desktop, open window and screen, build sprites
;
ExamineDesktops()
Global
desktop_width.i = DesktopWidth(0)
Global
desktop_height.i = DesktopHeight(0)
Global
desktop_depth.i = DesktopDepth(0)
;
;
open a maximized window the same size as the desktop and without a border
;
Global
w_main_h.i = OpenWindow(#w_main_nr,0,0,desktop_width,desktop_height,"Sprite
Engine",#PB_Window_BorderLess|#PB_Window_ScreenCentered|#PB_Window_Maximize)
;
;
open a screen the same size as the window (ie. covers the whole desktop)
;
OpenWindowedScreen(w_main_h.i,0,0,desktop_width,desktop_height,0,0,0,#PB_Screen_SmartSynchronization)
;
;
create the first image and sprite (i've given them the same number)
;
Global
i_vectoid_h = CreateImage(#i_vectoid,64,64,32)
StartDrawing(ImageOutput(#i_vectoid))
Box(0,0,63,63,RGB(0,0,0))
FrontColor(RGB(0,255,0))
LineXY(4,60,32,4)
LineXY(32,4,60,60)
LineXY(60,60,32,16)
LineXY(32,16,4,60)
StopDrawing()
CreateSprite(#i_vectoid,64,64,#PB_Sprite_Texture)
StartDrawing(SpriteOutput(#i_vectoid))
DrawImage(i_vectoid_h,0,0)
StopDrawing()
;
;
create the second image and sprite (again with the same number)
;
Global
i_rock_h = CreateImage(#i_rock,64,64,32)
StartDrawing(ImageOutput(#i_rock))
Box(0,0,63,63,RGB(0,0,0))
FrontColor(RGB(255,255,255))
LineXY(4,60,32,4)
LineXY(32,4,60,60)
LineXY(60,60,38,16)
LineXY(38,16,54,6)
LineXY(54,6,43,34)
LineXY(43,34,60,60)
LineXY(60,60,10,35)
LineXY(10,35,4,60)
StopDrawing()
CreateSprite(#i_rock,64,64,#PB_Sprite_Texture)
StartDrawing(SpriteOutput(#i_rock))
DrawImage(i_rock_h,0,0)
StopDrawing()
;
;
create four objects, first the player, then three rocks
;
;
the player is object number 0
;
CreateSprite3D(0,#i_vectoid)
With
object(0)
\alive = #True
; yes, it's alive
\sprite3d_nr = 0
; which sprite did we use?
\a = 0
; direction it faces
\x = desktop_width / 2
; in the middle of the screen
\y = desktop_height / 2
; in the middle of the screen
\z = 1
; normal size
EndWith
;
;
three rocks, objects 1 to 3
;
Global
n.i
For
n = 1 To 3
CreateSprite3D(n,#i_rock)
With object(n)
\alive = #True
; yes, it's alive
\sprite3d_nr = 1
; which sprite did we use?
\a = 0
; direction it faces
\x = desktop_width / 4*n ; spread over the
screen
\y = desktop_height * 0.75 ; below the player object
\z = 1
; normal size
EndWith
Next
n
;
;
endless loop handling events
;
Global
action.i = #f_none
Repeat
Global event = WindowEvent()
Select event
Case #PB_Event_CloseWindow
action = #f_exit
Case 0
;
; there were no events, so let's do our graphical stuff
;
ExamineKeyboard()
If KeyboardPushed(#PB_Key_Escape)
action = #f_exit
EndIf
;
; process all objects
;
ClearScreen(0)
Start3D()
For n = 0 To #objects_n
With object(n)
If \alive
DisplaySprite3D(\sprite3d_nr,\x,\y)
EndIf
EndWith
Next n
Stop3D()
;
; and show things on the screen
;
FlipBuffers()
;
EndSelect
Until
action = #f_exit
;
;
smoothly close screen and window
;
CloseScreen()
CloseWindow(#w_main_nr)
7.3
Octagonal fixed speed movement
Yes.
I am perfectly aware that the title of this section is wrong, but I just
couldn't come up with a better prase. (Chances like this
are rare :-)) Think of Pacman, and all those shoot-em-ups. The player controls
the character directly, the character moves at a constant speed, and there's
no inertia. Sort of, but I think you got the picture.
Note
to myself: still need to work on this part.
7.4
Vector based movement with inertia
Now,
let's move things 'asteroids' style. Assume every object is moving in a
specific direction with a specific speed in pixels per second (called a
'vector'). From this we can deduct how many pixels the object should move
in x- and y-direction, per frame. Let's go back to the
code we ended with in 7.2 and expand
our structure a little to contain information on the vector and the resulting
movement, like this:
Structure
object
alive.i
; set to #true to enable this object
sprite3d_nr.i
; 3d sprite used
a.f
; angle ie. object orientation
z.f
; object size factor (1 = original size)
x.f
; x-coordinate
y.f
; y-coordinate
v_v.f
; speed in pixels per second
v_a.f
; direction of movement
d_x.f
; movement in x-direction in pixels per frame
d_y.f
; movement in y-direction in pixels per frame
EndStructure
So, let's
say our oject moves 30 pixels per second, at an angle of 45 degrees. x-
and y- movement per frame can be calculated like this:
v_v
= 30
v_a
= 45
d_x
= v_v * Sin(v_a) / framerate
d_y
= v_v * Cos(v_a) / framerate
Unfortunately
we're dealing with a computer here, which means that position 0,0 is in
the top left corner of the screen, and sinus and cosinus use radians, not
degrees, so the final calculation in our code ends up like this:
\d_x
= \v_v * Sin( \v_a / 180 * #PI ) / desktop_framerate
\d_y
= 0 - \v_v * Cos( \v_a / 180 * #PI ) / desktop_framerate
Mmm. Whilst
we're at it, we'd better add some additional information to that structure,
which might make our life easier later on, such as which 3D sprite number
we use, if it rotates and at what speed, and let's add the size as well
so we can do proper screen wrapping (ie. if thinkgs disappear on the left
side they should reappear on the right side). This blows up the structure
like this:
Structure
object
alive.i
; set to #true to enable this object
sprite_nr.i
; sprite used
sprite3d_nr.i
; 3d sprite used
width.i
; width of sprite
height.i
; height of sprite
a.f
; angle ie. object orientation
r.f
; rotation speed in degrees per second
d_r.f
; rotation in degrees per frame
z.f
; object size factor (1 = original size)
v_v.f
; speed in pixels per second
v_a.f
; direction of movement
d_x.f
; movement in x-direction in pixels per frame
d_y.f
; movement in y-direction in pixels per frame
x.f
; x-coordinate
y.f
; y-coordinate
EndStructure
With all
the other changes our sample program has become 247 lines... But I think
you can handle it by now :-)
;
survival guide 7_2_300 moving objects
;
pb 4.51 directx9
;
EnableExplicit
;
;
*** initialize
;
InitSprite()
InitSprite3D()
InitKeyboard()
;
;
make sure our gui elements, images etc. have unique numbers
;
Enumeration
#w_main_nr
; main window
;
#f_exit
; quit
#f_none
; no action (keep looping)
;
#i_vectoid
; rocket 'v' shaped image
#i_rock
; rock image
;
EndEnumeration
;
;
create a structure that will contain all information on each object (sprite)
;
Structure
object
alive.i
; set to #true to enable this object
sprite_nr.i
; sprite used
sprite3d_nr.i
; 3d sprite used
width.i
; width of sprite
height.i
; height of sprite
a.f
; angle ie. object orientation
r.f
; rotation speed in degrees per second
d_r.f
; rotation in degrees per frame
z.f
; object size factor (1 = original size)
v_v.f
; speed in pixels per second
v_a.f
; direction of movement
d_x.f
; movement in x-direction in pixels per frame
d_y.f
; movement in y-direction in pixels per frame
x.f
; x-coordinate
y.f
; y-coordinate
EndStructure
;
;
create an array that can contain 100 objects
;
#objects_n
= 100
Dim
object.object(#objects_n) ; so
the array() is called 'object' and is of type 'object'
;
;
*** get information on the desktop, open window and screen, build sprites
;
ExamineDesktops()
Global
desktop_width.i = DesktopWidth(0)
Global
desktop_height.i = DesktopHeight(0)
Global
desktop_depth.i = DesktopDepth(0)
Global
desktop_framerate.i = DesktopFrequency(0)
;
;
open a maximized window the same size as the desktop and without a border
;
Global
w_main_h.i = OpenWindow(#w_main_nr,0,0,desktop_width,desktop_height,"Sprite
Engine",#PB_Window_BorderLess|#PB_Window_ScreenCentered|#PB_Window_Maximize)
;
;
open a screen the same size as the window (ie. covers the whole desktop)
;
OpenWindowedScreen(w_main_h.i,0,0,desktop_width,desktop_height,0,0,0,#PB_Screen_SmartSynchronization)
;
OpenScreen(desktop_width,desktop_height,32,"Sprite Engine",#PB_Screen_SmartSynchronization)
;
;
create the first image and sprite (i've given them the same number)
;
Global
i_vectoid_h = CreateImage(#i_vectoid,64,64,32)
StartDrawing(ImageOutput(#i_vectoid))
Box(0,0,63,63,RGB(0,0,0))
FrontColor(RGB(0,255,0))
LineXY(4,60,32,4)
LineXY(32,4,60,60)
LineXY(60,60,32,16)
LineXY(32,16,4,60)
StopDrawing()
CreateSprite(#i_vectoid,64,64,#PB_Sprite_Texture)
StartDrawing(SpriteOutput(#i_vectoid))
DrawImage(i_vectoid_h,0,0)
StopDrawing()
;
;
create the second image and sprite (again with the same number)
;
Global
i_rock_h = CreateImage(#i_rock,64,64,32)
StartDrawing(ImageOutput(#i_rock))
Box(0,0,63,63,RGB(0,0,0))
FrontColor(RGB(255,255,255))
LineXY(4,60,32,4)
LineXY(32,4,60,60)
LineXY(60,60,38,16)
LineXY(38,16,54,6)
LineXY(54,6,43,34)
LineXY(43,34,60,60)
LineXY(60,60,10,35)
LineXY(10,35,4,60)
StopDrawing()
CreateSprite(#i_rock,64,64,#PB_Sprite_Texture)
StartDrawing(SpriteOutput(#i_rock))
DrawImage(i_rock_h,0,0)
StopDrawing()
;
;
create four objects, first the player, then three rocks
;
;
the player is object number 0
;
With
object(0)
\alive = #True
; yes, it's alive
\sprite_nr = #i_vectoid
; wich 2d sprite do we use
\sprite3d_nr = 0
; which 3d sprite will we use
\a = 0
; direction it faces
\x = desktop_width / 2
; in the middle of the screen
\y = desktop_height / 2
; in the middle of the screen
\z = 1
; normal size
\v_v = 120
; move at 60 pixels per second
\v_a = 30
; in 30 degrees (left up)
\r = 0
EndWith
;
;
three rocks, objects 1 to 3
;
Global
n.i
For
n = 1 To 3
; change to create more rocks
With object(n)
\alive = #True
; yes, it's alive
\sprite_nr = #i_rock
; wich 2d sprite do we use
\sprite3d_nr = n
; which 3d sprite will we use
\a = 0
; direction it faces
\x = desktop_width / 4*n ; spread over the
screen
\y = desktop_height * 0.75 ; below the player object
\z = 1
; normal size
\v_v = Random(200)
; random speed
\v_a = Random(360)
; random direction
\r = Random(360)-Random(360) ; rotation speed in degrees per second
EndWith
Next
n
;
;
check starting orientation and speeds for all objects, create the 3d sprites,
and fill in some fields
;
For
n = 0 To #objects_n
With object(n)
If \alive = #True
;
; here we calculate the movement per frame for each object
;
\d_x = \v_v * Sin( \v_a / 180 * #PI ) / desktop_framerate
\d_y = 0 - \v_v * Cos( \v_a / 180 * #PI ) / desktop_framerate
;
; it's rotation speed per frame
;
\d_r = \r / desktop_framerate
;
; fill in the size
;
\width = SpriteWidth( \sprite_nr )
\height = SpriteHeight( \sprite_nr )
;
; and then create the 3d version and make sure it points in the right direction
;
CreateSprite3D( \sprite3d_nr , \sprite_nr )
RotateSprite3D( \sprite3d_nr , \v_a , #PB_Absolute)
;
EndIf
EndWith
Next
n
;
;
*** endless loop handling windows events and moving objects
;
Global
action.i = #f_none
Repeat
Global event = WindowEvent()
Select event
Case #PB_Event_CloseWindow
action = #f_exit
Case 0
;
; there were no events, so let's do our graphical stuff
;
ExamineKeyboard()
If KeyboardPushed(#PB_Key_Escape)
action = #f_exit
EndIf
;
; << insert here your controlling code >>
;
; process all objects
;
ClearScreen(0)
Start3D()
For n = 0 To #objects_n
With object(n)
If \alive
;
; here we actually add the movement per frame to the sprites coordinates
; and we handle screen wraps ie. the object appears on the other side of
; the screen if it leaves the visible area
;
; first apply x / y speeds
;
If \d_x <> 0
\x = \x + \d_x
If \x > desktop_width
\x = \x - desktop_width - \width
ElseIf \x < 0 - \width
\x = \x + desktop_width + \width
EndIf
EndIf
If \d_y <> 0
\y = \y + \d_y
If \y > desktop_height
\y = \y - desktop_height - \height
ElseIf \y < 0 - \height
\y = \y + desktop_height + \height
EndIf
EndIf
;
; now rotate any rotating objects
;
If \d_r <> 0
\a = \a + \d_r
If \a > 360
\a = \a - 360
ElseIf \a < 0
\a = \a +360
EndIf
RotateSprite3D( \sprite3d_nr , \a , #PB_Absolute )
EndIf
;
; and draw the actual sprite
;
DisplaySprite3D(\sprite3d_nr,\x,\y)
EndIf
EndWith
Next n
Stop3D()
;
; and show things on the screen
;
FlipBuffers()
;
EndSelect
Until
action = #f_exit
;
;
smoothly close screen and window
;
CloseScreen()
CloseWindow(#w_main_nr)
The next
challenge: add player control. Look in the code above for the line reading:
;
<< insert here your controlling code >>
It should
be around line 184. Replace that line with the following code:
;
;
player movement in two flavours
;
With
object(0)
;
; adjust speed and angle etc. depending on pressed key
;
If KeyboardPushed(#PB_Key_A)
\r = \r - 1000 / desktop_framerate
EndIf
If KeyboardPushed(#PB_Key_D)
\r = \r + 1000 / desktop_framerate
EndIf
If KeyboardPushed(#PB_Key_W)
\d_x = \d_x + 10 * Sin( \a / 180 * #PI) / desktop_framerate
\d_y = \d_y - 10 * Cos ( \a / 180 * #PI ) / desktop_framerate
EndIf
If KeyboardPushed(#PB_Key_S)
\d_x = \d_x - 10 * Sin( \a / 180 * #PI) / desktop_framerate
\d_y = \d_y + 10 * Cos ( \a / 180 * #PI ) / desktop_framerate
EndIf
;
; move the object with a fixed speed and no inertia
;
If KeyboardPushed(#PB_Key_Left)
\d_x = 0
\d_y = 0
\x = \x - 500 / desktop_framerate
EndIf
If KeyboardPushed(#PB_Key_Right)
\d_x = 0
\d_y = 0
\x = \x + 500 / desktop_framerate
EndIf
If KeyboardPushed(#PB_Key_Up)
\d_x = 0
\d_y = 0
\y = \y - 500 / desktop_framerate
EndIf
If KeyboardPushed(#PB_Key_Down)
\d_x = 0
\d_y = 0
\y = \y + 500 / desktop_framerate
EndIf
;
; here's how to retrieve the new vector, although I'm not using
; it in this code
;
\v_v = Sqr( \d_x * \d_x + \d_y * \d_y) * desktop_framerate
\v_a = 90+ATan( \d_y / \d_x ) * 180 / #PI
If \d_x < 0
\v_a = \v_a + 180
EndIf
;
; uncomment the next two lines if you want to verify my calculations above
; they would recalculate the new movements per frame based on the recalculated
vector
;
; \d_x = \v_v * Sin( \v_a / 180 * #PI ) / desktop_framerate
; \d_y = 0 - \v_v * Cos( \v_a / 180 * #PI ) / desktop_framerate
;
; re-calculate movement / rotation per frame
;
\d_r = \r / desktop_framerate
;
EndWith
7.5
Size frequently matters!
<<
werkpunt - this section is under construction >>
We
did miss some things. For example: screen resolution....
multi
monitor setups...
Resolution
We've
now figured out that moving an object should be done in a constant pixels
per second, and by figuring out the framerate we can now move it in pixels
per frame. But there's a catch... What if our program needs to look and
feel the same on every screen, not just those with a different refresh
rate, but also those with a different resolution? Obviously you'd have
to create graphics to match the different resolution, but you would also
have to adjust the movement per frame.
So,
instead of expressing the movement of our objects in pixels per second,
we'd better express them in 'percentage of screen per second'.
Duh?
Duh.
An
example: on our 50 hz 1024 x 768 screen we want to move the object with
120 pixels per second, ie. we need to move it:
120
pixels per second at 50 hz ->
120
/ 50 = 2.4 pixels per frame ->
from
left to right would take 1024 / 2.4 = 426.6 frames or 8.53 seconds
If we
would only look at the framerate, the same program on a 60 hz 800 x 600
screen would take:
120
pixels per second at 60 hz ->
120
/ 60 = 2 pixels per frame ->
from
left to right would take 800 / 2 = 400 frames or 6.66 seconds
Not good!
We
could also express speed as a 'percentage of the screen size':
1024
x 768 screen at 50 hz:
120
pixels per second at 60 hz on a 1024 x 768 screen ->
120
/ 1024 = 11.72% of the screen per second ->
11.72%
x 1024 / 50 = 2.4 pixels per frame
from
left to right would take 1024 / 2.4 = 426.6 frames or 8.53 seconds
800
x 600 screen at 60 hz:
11.72%
x 800 / 60 = 1.56 pixels per frame
from
left to right would take 800 / 1.56 = 512 frames or 8.53 seconds
Also,
the object on screen 2 should be resized. If it's 64 pixels on the 1024
x 768 screen, it should be about 64 / 1024 x 800 = 46.9 pixels on the 800
x 600 screen.
This
is why programming 'in the old days' of the C64, Atari 800 and so on was
so much easier: all machines were more or less identical. The same thing
applies to consoles: except for the NTSC vs. PAL issue they're all the
same. It also explains why many games do not properly support widescreen,
100hz, or other variations... It's just more work for the programmers.
Multi-monitor
setups
Another
interesting challenge is a multi monitor setup, where each monitor has
its own refresh rate and its own screen resolution. If the window (ie.
windowed screen) would be opened on the second monitor we should use that
monitor's resolution and a suitable framerate...
Got
a headache? Good! Now go out and write that award winning apllication or
game! :-)
|